import { ReactiveProperty } from './ReactiveProperty';
import { AnimationState } from 'cc';
import { Animation, animation, AnimationClip, __private } from 'cc';
import { Observable, Observer } from 'rxjs';

export type AnimationPlayOptions = {
    crossFade?: boolean,
    wrapMode?: __private._cocos_animation_types__WrapMode,
    time?: number,
}

export function AnimationCrossFadeNormal(anim: Animation, clipName?: string): Observable<any> {
    return AnimationPlay(anim, clipName ?? anim.defaultClip.name, { crossFade: true, wrapMode: AnimationClip.WrapMode.Normal });
}

export function AnimationCrossFadeReverse(anim: Animation, clipName?: string): Observable<any> {
    return AnimationPlay(anim, clipName ?? anim.defaultClip.name, { crossFade: true, wrapMode: AnimationClip.WrapMode.Reverse });
}

// 这个是兼容传统的写法
export function AnimationPlay(anim: Animation, clipName?: string, options?: AnimationPlayOptions): Observable<any> {
    return _AnimationPlay(anim, clipName, options).observable;
}

// 这个是可skip的写法
export function AnimationPlayWithSkip(anim: Animation, clipName?: string, options?: AnimationPlayOptions) {
    return _AnimationPlay(anim, clipName, options);
}

function _AnimationPlay(anim: Animation, clipName?: string, options?: AnimationPlayOptions): { observable: Observable<any>, skip: () => void } {
    let needSkip = ReactiveProperty.Create(false);
    let skip = () => needSkip.value = true;
    const observable = new Observable((observer: Observer<{ state: string, target: Animation, curLoop?: number }>) => {
        let state = anim.getState(clipName ?? anim.defaultClip.name);
        if (state == null) {
            observer.next({ target: anim, state: 'error' });
            observer.complete();
        }
        observer.next({ target: anim, state: 'info' });
        if (options?.wrapMode != null) state.wrapMode = options.wrapMode;
        let eventFinished = () => {
            observer.next({ state: Animation.EventType.FINISHED, target: anim });
            observer.complete();
        };
        anim.on(Animation.EventType.FINISHED, eventFinished);
        if (!needSkip.value) {
            if (options?.crossFade) {
                clipName == null ? anim.crossFade(anim.defaultClip.name) : anim.crossFade(clipName);
            } else {
                clipName == null ? anim.play() : anim.play(clipName);
            }
        }
        if (options?.time != null) state.setTime(options.time);
        needSkip.subscribeWhenTrue(() => {
            observer.next({ target: anim, state: 'skip' });
            observer.complete();
        });
        return () => {
            if (state?.isPlaying) state.stop();
            anim.off(Animation.EventType.FINISHED, eventFinished);
        }
    });
    return { observable, skip };
}

export function AnimationPlayWithEvent(anim: Animation, clipName?: string, loop?: number): Observable<{ state: string, target: Animation, eventName?: string, eventParam?: any, curLoop?: number }> {
    return new Observable((observer: Observer<{ state: string, target: Animation, eventName?: string, eventParams?: string[], curLoop?: number }>) => {
        let curLoop = 0;
        let state = anim.getState(clipName ?? anim.defaultClip.name);
        let eventLastFrame = () => {
            observer.next({ state: Animation.EventType.LASTFRAME, target: anim, curLoop: curLoop });
            if (loop && curLoop + 1 >= loop) {
                if (state.isPlaying) state.stop();
                observer.next({ state: Animation.EventType.FINISHED, target: anim, curLoop: curLoop });
                observer.complete();
            }
            curLoop++;
        };
        let eventFinished = () => {
            observer.next({ state: Animation.EventType.FINISHED, target: anim, curLoop: curLoop });
            observer.complete();
        };
        anim.on(Animation.EventType.FINISHED, eventFinished);
        anim.on(Animation.EventType.LASTFRAME, eventLastFrame);

        for (let i = 0; i < anim.clips.length; i++) {
            let clip = anim.clips[i];
            for (let j = 0; j < clip.events.length; j++) {
                let event = clip.events[j];
                let eventName = event.func;
                let eventParams = event.params;
                if (eventName) {
                    anim[eventName] = () => {
                        observer.next({ state: 'event', target: anim, eventName, eventParams, curLoop });
                    };
                }
            }
        }

        clipName == null ? anim.play() : anim.play(clipName);
        return () => {
            if (state.isPlaying) state.stop();
            anim.off(Animation.EventType.FINISHED, eventFinished);
            anim.off(Animation.EventType.LASTFRAME, eventLastFrame);
        }
    });
};